/*-------------------------------------------------------------------------
 *
 * parse_merge.c
 *	  在解析器中处理合并语句
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/parser/parse_merge.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/sysattr.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/analyze.h"
#include "parser/parse_collate.h"
#include "parser/parsetree.h"
#include "parser/parser.h"
#include "parser/parse_clause.h"
#include "parser/parse_cte.h"
#include "parser/parse_expr.h"
#include "parser/parse_merge.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "utils/rel.h"
#include "utils/relcache.h"

static void fc_setNamespaceForMergeWhen(ParseState *fc_pstate,
									 MergeWhenClause *fc_mergeWhenClause,
									 Index fc_targetRTI,
									 Index fc_sourceRTI);
static void fc_setNamespaceVisibilityForRTE(List *fc_namespace, RangeTblEntry *fc_rte,
										 bool fc_rel_visible,
										 bool fc_cols_visible);

/*
 * 在转换个别动作的 quals 和 targetlist 表达式时，对命名空间的可见性进行适当更改。
 * 特别是，对于 INSERT 动作，我们必须只看到源关系（因为 INSERT 动作
 * 针对的是未匹配的元组，因此没有目标元组可处理）。另一方面，
 * UPDATE 和 DELETE 动作可以看到源和目标关系。
 *
 * 此外，由于内部连接节点可能隐藏源和目标关系，
 * 我们必须明确使各自的关系可见，以便可以无修饰地引用这些关系中的列。
 */
static void fc_setNamespaceForMergeWhen(ParseState *fc_pstate, MergeWhenClause *fc_mergeWhenClause,
						 Index fc_targetRTI, Index fc_sourceRTI)
{
	RangeTblEntry *fc_targetRelRTE,
			   *fc_sourceRelRTE;

	fc_targetRelRTE = rt_fetch(fc_targetRTI, fc_pstate->p_rtable);
	fc_sourceRelRTE = rt_fetch(fc_sourceRTI, fc_pstate->p_rtable);

	if (fc_mergeWhenClause->matched)
	{
		Assert(fc_mergeWhenClause->commandType == CMD_UPDATE ||
			   fc_mergeWhenClause->commandType == CMD_DELETE ||
			   fc_mergeWhenClause->commandType == CMD_NOTHING);

		/* MATCHED 动作可以看到目标和源关系。 */
		fc_setNamespaceVisibilityForRTE(fc_pstate->p_namespace,
									 fc_targetRelRTE, true, true);
		fc_setNamespaceVisibilityForRTE(fc_pstate->p_namespace,
									 fc_sourceRelRTE, true, true);
	}
	else
	{
		/*
		 * NOT MATCHED 动作不能看到目标关系，但可以看到
		 * 源关系。
		 */
		Assert(fc_mergeWhenClause->commandType == CMD_INSERT ||
			   fc_mergeWhenClause->commandType == CMD_NOTHING);
		fc_setNamespaceVisibilityForRTE(fc_pstate->p_namespace,
									 fc_targetRelRTE, false, false);
		fc_setNamespaceVisibilityForRTE(fc_pstate->p_namespace,
									 fc_sourceRelRTE, true, true);
	}
}

/*
 * transformMergeStmt -
 *	  转换 MERGE 语句
 */
Query * transformMergeStmt(ParseState *fc_pstate, MergeStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	ListCell   *fc_l;
	AclMode		fc_targetPerms = ACL_NO_RIGHTS;
	bool		fc_is_terminal[2];
	Index		fc_sourceRTI;
	List	   *fc_mergeActionList;
	Node	   *fc_joinExpr;
	ParseNamespaceItem *fc_nsitem;

	/* 不能有外部的 WITH 需要考虑 */
	Assert(fc_pstate->p_ctenamespace == NIL);

	fc_qry->commandType = CMD_MERGE;
	fc_qry->hasRecursive = false;

	/* 独立于其他内容处理 WITH 子句 */
	if (fc_stmt->withClause)
	{
		if (fc_stmt->withClause->recursive)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("WITH RECURSIVE is not supported for MERGE statement")));

		fc_qry->cteList = transformWithClause(fc_pstate, fc_stmt->withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	/*
	 * 检查 WHEN 子句的权限和合理性
	 */
	fc_is_terminal[0] = false;
	fc_is_terminal[1] = false;
	foreach(fc_l, fc_stmt->mergeWhenClauses)
	{
		MergeWhenClause *fc_mergeWhenClause = (MergeWhenClause *) lfirst(fc_l);
		int			fc_when_type = (fc_mergeWhenClause->matched ? 0 : 1);

		/*
		 * 收集权限以进行检查，根据操作类型。我们要求
		 * DO NOTHING 的 SELECT 特权，因为如果 DO NOTHING
		 * 是唯一操作，目标关系检查的权限为零会不正常。
		 * 这不会造成损害：任何有意义的 MERGE 命令无论如何
		 * 至少需要对该表的某些访问权限。
		 */
		switch (fc_mergeWhenClause->commandType)
		{
			case CMD_INSERT:
				fc_targetPerms |= ACL_INSERT;
				break;
			case CMD_UPDATE:
				fc_targetPerms |= ACL_UPDATE;
				break;
			case CMD_DELETE:
				fc_targetPerms |= ACL_DELETE;
				break;
			case CMD_NOTHING:
				fc_targetPerms |= ACL_SELECT;
				break;
			default:
				elog(ERROR, "unknown action in MERGE WHEN clause");
		}

		/*
		 * 检查不可达的 WHEN 子句
		 */
		if (fc_is_terminal[fc_when_type])
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
		if (fc_mergeWhenClause->condition == NULL)
			fc_is_terminal[fc_when_type] = true;
	}

	/*
	 * 设置 MERGE 目标表。目标表将在下面的命名空间中添加
	 * 并在 transform_MERGE_to_join 中添加到 joinlist，因此不要
	 * 在这里执行该操作。
	 */
	fc_qry->resultRelation = setTargetTable(fc_pstate, fc_stmt->relation,
										 fc_stmt->relation->inh,
										 false, fc_targetPerms);

	/*
	 * 在各种情况下不支持 MERGE
	 */
	if (fc_pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
		fc_pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot execute MERGE on relation \"%s\"",
						RelationGetRelationName(fc_pstate->p_target_relation)),
				 errdetail_relkind_not_supported(fc_pstate->p_target_relation->rd_rel->relkind)));
	if (fc_pstate->p_target_relation->rd_rules != NULL &&
		fc_pstate->p_target_relation->rd_rules->numLocks > 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot execute MERGE on relation \"%s\"",
						RelationGetRelationName(fc_pstate->p_target_relation)),
				 errdetail("MERGE is not supported for relations with rules.")));

	/* 现在转换源关系以生成源 RTE。 */
	transformFromClause(fc_pstate,
						list_make1(fc_stmt->sourceRelation));
	fc_sourceRTI = list_length(fc_pstate->p_rtable);
	fc_nsitem = GetNSItemByRangeTablePosn(fc_pstate, fc_sourceRTI, 0);

	/*
	 * 检查目标表是否与源表冲突。
	 * 这通常是 checkNameSpaceConflicts 调用，但我们想要一个
	 * 更具体的错误消息。
	 */
	if (strcmp(fc_pstate->p_target_nsitem->p_names->aliasname,
			   fc_nsitem->p_names->aliasname) == 0)
		ereport(ERROR,
				errcode(ERRCODE_DUPLICATE_ALIAS),
				errmsg("name \"%s\" specified more than once",
					   fc_pstate->p_target_nsitem->p_names->aliasname),
				errdetail("The name is used both as MERGE target table and data source."));

	/*
	 * 这里不需要目标列表；它将在以后的 preprocess_targetlist 中设置。
	 */
	fc_qry->targetList = NIL;
	fc_qry->rtable = fc_pstate->p_rtable;

	/*
	 * 转换连接条件。这包括对目标方的引用，因此将其添加到
	 * 命名空间中。
	 */
	addNSItemToQuery(fc_pstate, fc_pstate->p_target_nsitem, false, true, true);
	fc_joinExpr = transformExpr(fc_pstate, fc_stmt->joinCondition,
							 EXPR_KIND_JOIN_ON);

	/*
	 * 使用仅基于源关系构建的 joinlist 创建临时查询的 jointree；
	 * 目标关系不包括在内。我们使用的 quals 是到合并目标的连接条件。
	 * 连接将完全由 transform_MERGE_to_join 构建。
	 */
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, fc_joinExpr);

	/*
	 * 我们现在有了良好的查询结构，现在查看 WHEN 条件和
	 * 操作目标列表。
	 *
	 * 总体而言，MERGE 查询的目标列表为 NIL。
	 *
	 * 每个单独的操作都有自己的目标列表，需要单独转换。
	 * 这些转换不会对整体目标列表做任何事情，因为该列表
	 * 仅用于 resjunk 列。
	 *
	 * 我们可以引用 Target 或 Source 中的任何列，这没问题，因为
	 * 这两者已经有 RTE。没有 EXCLUDED
	 * 伪关系用于 INSERT ON CONFLICT。
	 */
	fc_mergeActionList = NIL;
	foreach(fc_l, fc_stmt->mergeWhenClauses)
	{
		MergeWhenClause *fc_mergeWhenClause = lfirst_node(MergeWhenClause, fc_l);
		MergeAction *fc_action;

		fc_action = makeNode(MergeAction);
		fc_action->commandType = fc_mergeWhenClause->commandType;
		fc_action->matched = fc_mergeWhenClause->matched;

		/* 如果命令中存在任何 INSERT 操作，则使用外连接。 */
		if (fc_action->commandType == CMD_INSERT)
			fc_qry->mergeUseOuterJoin = true;

		/*
		 * 为特定操作设置命名空间。必须在分析 WHEN quals 和
		 * 操作目标列表之前执行此操作。
		 */
		fc_setNamespaceForMergeWhen(fc_pstate, fc_mergeWhenClause,
								 fc_qry->resultRelation,
								 fc_sourceRTI);

		/*
		 * 转换 WHEN 条件。
		 *
		 * 请注意，这些 quals 不会被添加到连接 quals 中；相反，它们
		 * 在执行期间单独评估，以决定执行 WHICH WHEN MATCHED
		 * 或 WHEN NOT MATCHED 操作。
		 */
		fc_action->qual = transformWhereClause(fc_pstate, fc_mergeWhenClause->condition,
											EXPR_KIND_MERGE_WHEN, "WHEN");

		/*
		 * 转换每个 INSERT 和 UPDATE 操作语句的目标列表
		 */
		switch (fc_action->commandType)
		{
			case CMD_INSERT:
				{
					List	   *fc_exprList = NIL;
					ListCell   *fc_lc;
					RangeTblEntry *fc_rte;
					ListCell   *fc_icols;
					ListCell   *fc_attnos;
					List	   *fc_icolumns;
					List	   *fc_attrnos;

					fc_pstate->p_is_insert = true;

					fc_icolumns = checkInsertTargets(fc_pstate,
												  fc_mergeWhenClause->targetList,
												  &fc_attrnos);
					Assert(list_length(fc_icolumns) == list_length(fc_attrnos));

					fc_action->override = fc_mergeWhenClause->override;

					/*
					 * 像处理 transformInsertStmt 中的 INSERT 一样处理 INSERT
					 */
					if (fc_mergeWhenClause->values == NIL)
					{
						/*
						 * 我们有 INSERT ... DEFAULT VALUES。我们可以通过
						 * 发送一个空目标列表来处理这个情况——所有
						 * 列将在规划器扩展
						 * 目标列表时默认值。
						 */
						fc_exprList = NIL;
					}
					else
					{
						/*
						 * 处理带有单个 VALUES 子列表的 INSERT ... VALUES。
						 * 我们单独处理此情况以提高效率。
						 * 该子列表直接计算为查询的目标列表，无需 VALUES RTE。
						 * 因此它的工作方式类似于没有任何 FROM 的 SELECT。
						 */

						/*
						 * 执行基本表达式转换（与 ROW() 表达式相同，但允许
						 * 在最顶层使用 SetToDefault）
						 */
						fc_exprList = transformExpressionList(fc_pstate,
														   fc_mergeWhenClause->values,
														   EXPR_KIND_VALUES_SINGLE,
														   true);

						/* 准备行以分配给目标表 */
						fc_exprList = transformInsertRow(fc_pstate, fc_exprList,
													  fc_mergeWhenClause->targetList,
													  fc_icolumns, fc_attrnos,
													  false);
					}

					/*
					 * 使用计算出的表达式列表生成操作的目标列表。
					 * 此外，将所有目标列标记为需要插入权限。
					 */
					fc_rte = fc_pstate->p_target_nsitem->p_rte;
					forthree(fc_lc, fc_exprList, fc_icols, fc_icolumns, fc_attnos, fc_attrnos)
					{
						Expr	   *fc_expr = (Expr *) lfirst(fc_lc);
						ResTarget  *fc_col = lfirst_node(ResTarget, fc_icols);
						AttrNumber	fc_attr_num = (AttrNumber) lfirst_int(fc_attnos);
						TargetEntry *fc_tle;

						fc_tle = makeTargetEntry(fc_expr,
											  fc_attr_num,
											  fc_col->name,
											  false);
						fc_action->targetList = lappend(fc_action->targetList, fc_tle);

						fc_rte->insertedCols =
							bms_add_member(fc_rte->insertedCols,
										   fc_attr_num - FirstLowInvalidHeapAttributeNumber);
					}
				}
				break;
			case CMD_UPDATE:
				{
					fc_pstate->p_is_insert = false;
					fc_action->targetList =
						transformUpdateTargetList(fc_pstate,
												  fc_mergeWhenClause->targetList);
				}
				break;
			case CMD_DELETE:
				break;

			case CMD_NOTHING:
				fc_action->targetList = NIL;
				break;
			default:
				elog(ERROR, "unknown action in MERGE WHEN clause");
		}

		fc_mergeActionList = lappend(fc_mergeActionList, fc_action);
	}

	fc_qry->mergeActionList = fc_mergeActionList;

	/* 返回可能在未来添加，但不在 SQL 标准中 */
	fc_qry->returningList = NULL;

	fc_qry->hasTargetSRFs = false;
	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;

	assign_query_collations(fc_pstate, fc_qry);

	return fc_qry;
}

static void fc_setNamespaceVisibilityForRTE(List *fc_namespace, RangeTblEntry *fc_rte,
							 bool fc_rel_visible,
							 bool fc_cols_visible)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_namespace)
	{
		ParseNamespaceItem *fc_nsitem = (ParseNamespaceItem *) lfirst(fc_lc);

		if (fc_nsitem->p_rte == fc_rte)
		{
			fc_nsitem->p_rel_visible = fc_rel_visible;
			fc_nsitem->p_cols_visible = fc_cols_visible;
			break;
		}
	}
}
